/*
 * This file is part of freewm.
 *
 * freewm is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * freewm is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with freewm.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "freewm.h"
#include "wm.h"
#include "module.h"

// Window linked list.
struct win_link
{
	// Next and previous nodes.
	struct win_link *next;
	struct win_link *prev;
	
	struct wm_client c;
};

// WM globals.
struct win_link *win_beg = NULL, *win_end = NULL;
Window win_focused = 0;
unsigned int workspace_current = 0;
unsigned int workspace_total = 1;

// Check if a window exists in our linked list.
struct wm_client *wm_find(Window w)
{
	logger("Checking if window %d exists", w);
	
	struct win_link *cur = win_beg;
	while (cur)
	{
		if (!cur->c.win || !cur->c.frame) return NULL;
		
		// Return requested window.
		if (cur->c.win == w || cur->c.frame == w) return &cur->c;
		
		// Go to next node.
		cur = cur->next;
	}
	
	logger("Done checking if window %d exists", w);
	return NULL;
}

// Add a window to the linked list.
void wm_add(Window w, Window nw)
{
	logger("wm_add: %d -> %d", w, nw);
	
	// Build new link.
	struct win_link *lnk = (struct win_link *) malloc(sizeof(struct win_link));
	struct wm_client c;
	
	c.win = w;
	c.frame = nw;
	c.ignore_unmap = 0;
	c.workspace = workspace_current;
	c.mapped = 0;
	
	lnk->c = c;
	lnk->next = NULL;
	
	logger("wm_add %d %d", lnk->c.frame, lnk->c.win);
	
	// Add link to end of list.
	if (win_end)
	{
		lnk->prev = win_end;
		win_end->next = lnk;
		win_end = lnk;
	}
	// First node!
	else
	{
		win_beg = lnk;
		win_end = lnk;
	}
}

// Remove a window from the linked list.
void wm_del(struct wm_client *c, enum wm_del_type type)
{
	logger("wm_del: %d", c->frame);
	
	struct win_link *cur = win_beg;
	while (cur)
	{
		// Found the alias to delete.
		if (cur->c.frame == c->frame)
		{
			logger("Deleting win: %d", c->frame);
			
			if (cur->prev) cur->prev->next = cur->next;
			if (cur->next) cur->next->prev = cur->prev;
			if (cur == win_beg) win_beg = cur->next;
			if (cur == win_end) win_end = cur->prev;
			
			// Free resources.
			free(cur);
			logger("JUST FREED");
			
			break;
		}
		
		// Go to next node.
		cur = cur->next;
	}
	
	switch (type)
	{
		case WM_DEL_UNPARENT:
			logger("Unparenting window...");
			// FIX THIS, make the x and y pos the current window's
			// x and y pos.
			XReparentWindow(dpy, c->win, root, 0, 0);
			XRemoveFromSaveSet(dpy, c->win);
			
		case WM_DEL_NORMAL:
			logger("Bout to destroy it!");
			// Destroy this window. (Should be the parent encapsulating window)
			XDestroyWindow(dpy, c->frame);
			logger("Destroyed!");
			break;
	}
}

// Reparent a new window.
void wm_new(Window w)
{
	logger("Creating new window: %d", w);
	
	// Retrieve the attributes of this window.
	logger("XGetWindowAttributes");
	XWindowAttributes attr;
	XGetWindowAttributes(dpy, w, &attr);
	
	// Create the encapsulating window.
	XSetWindowAttributes pattr;
	pattr.override_redirect = 1;
	pattr.event_mask = StructMask|ButtonMask|KeyMask|ExposureMask|EnterWindowMask;
	
	logger("XCreateWindow...");
	Window nw = XCreateWindow(	dpy, root,
								attr.x, attr.y, attr.width, attr.height,
								0,
								DefaultDepth(dpy, screen),
								CopyFromParent,
								DefaultVisual(dpy, screen),
								CWOverrideRedirect|CWEventMask, &pattr);

	// Add to linked list.
	logger("Calling wm_add");
	wm_add(w, nw);

	// Reparent
	logger("XReparentWindow");
	XAddToSaveSet(dpy, w);
	XSelectInput(dpy, w, ColormapChangeMask|PropertyChangeMask);
	XReparentWindow(dpy, w, nw, 0, 0);
}

// Get a window by position in linked list.
// (This will most likely be for modules trying to retrieve all windows.)
struct wm_client *wm_get(int pos)
{
	struct win_link *cur = win_beg;
	unsigned int c = 0;
	
	while (cur)
	{
		// Return requested window.
		if (c == pos) return &cur->c;
		
		// Go to next node.
		cur = cur->next;
		c++; // Whoa!
	}
	
	return 0;
}

// Look for existing windows.
// (These are usually windows already loaded by xinitrc)
// Thanks to aewm, which I pretty much just copied this method from.
void wm_scan_premapped(void)
{
	logger("Scanning premapped...");
	
	Window *wins, root_ret, parent_ret;
	unsigned int i, win_num;
	XWindowAttributes attr;

	// Finds all X windows.
	XQueryTree(	dpy, root,
				&root_ret, &parent_ret,
				&wins, &win_num);
	
	// Add each of these windows.
	for (i = 0; i < win_num; i++)
	{
		XGetWindowAttributes(dpy, wins[i], &attr);
		
		// Check that this window wants to be controlled by the WM.
		if (!attr.override_redirect)
		{
			logger("Found premapped: %d", wins[i]);
			wm_new(wins[i]);
		}
	}
	
	XFree(wins);
}

// Get the currently focused window.
Window wm_focused(void)
{
	return win_focused;
}

// Set the currently focused window.
void wm_focus(Window w)
{
	win_focused = w;
	
	// Trigger wm_focus binds.
	module_bind_exec("wm_focus");
}

// Add a workspace.
void wm_workspace_add(void)
{
	workspace_total++;
	
	// Alert that a workspace has been added.
	module_bind_exec("wm_workspace_add");
}

// Remove a workspace.
void wm_workspace_del(void)
{
	if (workspace_total > 1)
		workspace_total--;
	
	// Alert that a workspace has been deleted.  May be useful...
	module_bind_exec("wm_workspace_del");
}

// Set number of workspaces.
void wm_workspaces_set(int num)
{
	if (num >= 1)
		workspace_total = num;
}

// Get the current workspace number.
int wm_workspace(void)
{
	return workspace_current;
}

// Get the total amount of workspaces.
int wm_workspaces(void)
{
	return workspace_total;
}

// Goto workspace number.
void wm_workspace_goto(int num)
{
	if (num >= 0 && num <= (workspace_total - 1))
	{
		workspace_current = num;
		
		// Map all clients in current workspace and unmap all clients not.
		// SHOULD THIS BE PART OF THE CORE?  Seems like maybe not.
		// I will deliberate later on.
		struct wm_client *c;
		int i = 0;
		
		while ((c = wm_get(i++)) != NULL)
		{
			if (c->workspace != workspace_current && c->mapped)
			{
				c->ignore_unmap = 1;
				c->mapped = 0;
				
				XUnmapWindow(dpy, c->frame);
			}
			else if (c->workspace == workspace_current && !c->mapped)
			{
				c->ignore_unmap = 0;
				c->mapped = 1;
				
				XMapWindow(dpy, c->frame);
			}
		}
		
		module_bind_exec("wm_workspace_change");
	}
}

// Goto next workspace.
void wm_workspace_next(void)
{
	wm_workspace_goto(workspace_current + 1);
}

// Goto previous workspace.
void wm_workspace_prev(void)
{
	wm_workspace_goto(workspace_current - 1);
}
